package jinyilw.tools.net.dev;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;

public class PacketManager
{

	public int BUFFER_SIZE = 2048;

	private short[] registeredID = new short[0];
	private Object[] registeredObject = new Object[0];
	private DataManipulator[] manipulator = new DataManipulator[]{
			new DataEncryption(), new DataObfuscation()};

	public PacketManager()
	{
	}

	public void registerPacket(short id, NetworkPacket packet)
	{
		if (packet.getID() == NetworkPacket.NULL_ID)
		{
			throw new RuntimeException("Packet ID " + packet + " must be set "
					+ "either by passing NetworkPacket.setID(short) or "
					+ "by overriding NetworkPacket.getID() method");
		}

		for (int i = 0; i < this.registeredID.length; i++)
		{
			if (this.registeredID[i] == id)
			{
				throw new RuntimeException(
						"ID " + id + " is already registered with "
								+ this.registeredObject[i]);
			}
		}

		this.registeredID = (short[]) NetworkUtil.expand(this.registeredID, 1);
		this.registeredObject = (Object[]) NetworkUtil
				.expand(this.registeredObject, 1);

		int len = this.registeredID.length - 1;
		this.registeredID[len] = id;
		this.registeredObject[len] = packet;
	}

	public void registerPacket(short id, Class<?> packetClass)
	{
		if (!NetworkPacket.class.isAssignableFrom(packetClass))
		{
			throw new RuntimeException("Unable to register packet class "
					+ packetClass + ", not subclass of NetworkPacket class.");
		}

		for (int i = 0; i < this.registeredID.length; i++)
		{
			if (this.registeredID[i] == id)
			{
				throw new RuntimeException(
						"ID " + id + " is already registered with "
								+ this.registeredObject[i]);
			}
		}

		this.registeredID = (short[]) NetworkUtil.expand(this.registeredID, 1);
		this.registeredObject = (Object[]) NetworkUtil
				.expand(this.registeredObject, 1);

		int len = this.registeredID.length - 1;
		this.registeredID[len] = id;
		this.registeredObject[len] = packetClass;
	}

	public synchronized byte[] pack(NetworkPacket packet) throws IOException
	{
		ByteArrayOutputStream baout = new ByteArrayOutputStream();
		DataOutputStream output = new DataOutputStream(baout);

		output.writeShort(this.getPacketID(packet));

		if (packet.isCompressed())
		{
			output = this.compress(output);
		}

		packet.writePacket(output);
		output.close();

		byte[] data = baout.toByteArray();
		if (this.manipulator != null)
		{
			try
			{
				for (DataManipulator dataManipulator : this.manipulator)
				{
					data = dataManipulator.manipulate(data);
				}

			} catch (Exception ex)
			{
				ex.printStackTrace();
				throw new IOException(ex.getMessage());
			}
		}

		return data;
	}

	public synchronized NetworkPacket unpack(byte[] data) throws IOException
	{
		if (this.manipulator != null)
		{
			try
			{
				for (int i = this.manipulator.length - 1; i >= 0; i--)
				{
					data = this.manipulator[i].deManipulate(data);
				}

			} catch (Exception ex)
			{
				ex.printStackTrace();
				throw new IOException(ex.getMessage());
			}
		}

		ByteArrayInputStream bain = new ByteArrayInputStream(data);
		DataInputStream input = new DataInputStream(bain);
		short packetID = input.readShort();
		NetworkPacket packet;
		if (packetID == NetworkPing.ID)
		{
			packet = NetworkPing.getInstance();
		} else
		{
			if (packetID == CommandPacket.ID)
				packet = new CommandPacket();
			else if (packetID == NetworkMessage.ID)
				packet = new NetworkMessage();
			else
				packet = createPacket(packetID);
			if (packet.isCompressed())
				input = this.decompress(input);
			packet.readPacket(input);
		}
		input.close();
		return packet;
	}

	protected short getPacketID(NetworkPacket packet)
	{
		if (packet.getID() != NetworkPacket.NULL_ID)
		{
			return packet.getID();
		}
		Class<?> packetClass = packet.getClass();

		for (int i = 0; i < this.registeredID.length; i++)
		{
			if (packetClass.equals(this.registeredObject[i]))
			{
				return this.registeredID[i];
			}
		}
		throw new RuntimeException(packetClass
				+ " class has not been registered.\n"
				+ "Use NetworkConfig.registerPacket(short id, Class packetClass) to register the "
				+ "packet id.");
	}

	protected NetworkPacket createPacket(short id)
	{
		NetworkPacket packet = null;
		for (int i = 0; i < this.registeredID.length; i++)
		{
			if (this.registeredID[i] == id)
			{
				Object type = this.registeredObject[i];

				if (type instanceof Class)
				{
					try
					{
						packet = (NetworkPacket) ((Class<?>) type).newInstance();

					} catch (Exception ex)
					{
						throw new RuntimeException(ex);
					}
				} else
				{
					try
					{
						packet = (NetworkPacket) ((NetworkPacket) type).clone();

					} catch (Exception ex)
					{
						throw new RuntimeException(ex);
					}
				}
				break;
			}
		}
		if (packet == null)
		{
			throw new RuntimeException("ID " + id
					+ " has not been registered.\n"
					+ "Use NetworkConfig.registerPacket(short id, Class packetClass) to register "
					+ "the packet id.");
		}

		packet.setId(id);
		return packet;
	}

	protected DataOutputStream compress(DataOutputStream output)
			throws IOException
	{
		return new DataOutputStream(
				new GZIPOutputStream(output, this.BUFFER_SIZE));
	}

	protected DataInputStream decompress(DataInputStream input)
			throws IOException
	{
		return new DataInputStream(
				new GZIPInputStream(input, this.BUFFER_SIZE));
	}

	public short[] getRegisteredID()
	{
		return this.registeredID;
	}

	public Object[] getRegisteredObject()
	{
		return this.registeredObject;
	}

	public DataManipulator[] getDataManipulator()
	{
		return this.manipulator;
	}

	public void setDataManipulator(DataManipulator[] dataManipulator)
	{
		this.manipulator = dataManipulator;
	}
}
